package main

import (
	"fmt"
	"time"
)

/*===worker pool（goroutine池）===
- 使用可以指定启动的goroutine数量–worker pool模式，控制goroutine的数量，防止goroutine泄漏和暴涨
- 需要明确的事， 运行时 通过 goroutine 来调用cpu处理任务，channel(chan) 仅仅只是负责 不同的 goroutine 间的通信
- 注意：
	- 通道关闭了，仍然可以从通道读取值，直至读完通道中的数据
	- main线程中，涉及channel存值与取值的代码片段应该看作非串行处理来理解，
    如下例中，worker向channel存值操作与外部for循环取channel中的值的操作，有代码来看逻辑上worker
	完成了所有存值操作后，一次性执行下方从channel取值的操作，但实际上是只要worker中完成了一个存值，
	就能从channel中取值
*/
func main() {
	/* [01] 处始化两个有缓存通道对象 */
	jobs := make(chan int, 100)
	results := make(chan int, 100)
	/* [02] 创建5个任务 */
	for i := 1; i <= 5; i++ {
		jobs <- i
	}

	/* [03] 开启3个goroutine */
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}
	close(jobs)
	/* [04] 输出结果,每次循环都会从通道中取出 当前 已经存入的值 */
	for a := 1; a <= 5; a++ {
		result := <-results
		fmt.Println("result=", result)
	}
	/*
		//for range 循环会去检测channel是否关闭，然后退出取值，
		//由于我们没有手动关闭results通道，所以 当results中值取完后还会一直去取值，从空通道中一直取值，导致死锁
		for result := range results {
			fmt.Print("result=",result)
		}
	*/
}

/* [重要] (mxn)3x5 处理任务过程分析 */
/**
======================================================================================
	1. GPM(goruntine,process负责调度,machine)，M是go对内核线程的模拟，一个M对应一个内核线程，
	goruntine最终放到M上执行
	2. go 的 goruntine 调度 是在用户态以队列形式完成的
	3. goruntine 通过 channel 共享数据；若一个channel取出了数据，其他使用该channel的goruntine
		只会操作channel中剩下的数据
======================================================================================
3x5分析：1. 先取3个job以队列形式交由3个goruntine处理(3个goruntine)
		2. 在worker方法中，(实际不是，因为Sleep)假设把循环jobs的某一次的值j与将j*2写入results通道看作一组原子操作
		3. jobs中有5个值(1,2,3,4,5)，当一个goruntine处理1时，其他两个goruntine会从剩下的4个值中选一个值处理，
		因为channel中值个数减少对3个goruntine是可见的；
		4. 3个goruntine处理3个job，第一个处理完的会从剩下的2个job里面随机处理一个，第二个处理完的会处理最后一个job
		，其中goruntine处理哪个job是在用户态runtime时通过go调度实现的，没有内核态与用户态切换，所以性能以及效率快
======================================================================================
*/
func worker(id int, jobs <-chan int, results chan<- int) {
	//3 个 goruntine 处理5个任务，每个任务处理完 存入 results 通道中
	for j := range jobs {
		fmt.Printf("worker:%d start job:%d\n", id, j)
		/* 阻塞当前 goruntine，给其他 2个 goruntine 执行机会*/
		time.Sleep(time.Second)
		fmt.Printf("worker:%d end job:%d\n", id, j)
		/* [tip] 此处向channel里面存值 */
		results <- j * 2
	}
}
